﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace KKSGameLibrary.Logic
{
    public class CardMap
    {
        CardInMap[,] Cards { set; get; }
        public List<RegionInMap> Regions { private set; get; }
        public IList<GamePlayer> Players {private set; get; }

        public static int SIZE = 25;
        //public static int CENTRAL = SIZE / 2;

        //Player GetPlayer(string playerid)
        //{
        //    return Players.FirstOrDefault
        //        (p => p.ID == playerid);
        //}

        //public int GetPlayerOwnedRegionCount(Player player)
        //{
        //    return (from region in Regions
        //            where region.Owners.Contains(player)
        //            select region).Count();
        //}

        public CardMap(IList<GamePlayer> players,int puppetnumber)
        {
            Players = players;
            foreach (var player in Players)
                player.PuppetRemain = puppetnumber;

            Regions=new List<RegionInMap>();
            Cards = new CardInMap[SIZE, SIZE];
        }

        public bool TestSetOwner(RegionInMap region, GamePlayer player)
        {
            if (player.PuppetRemain <= 0)
                return false;

            if (region.OwnerPuppets.Count == 0)
                return true;
            else
                return false;
        }

        public bool TestSetOwner(int row,int col,OpenDirection dir, GamePlayer player)
        {
            var region = GetRegion(row, col, dir);
            if (region != null)
            {
                return TestSetOwner(region, player);
            }
            return false;
        }

        public void SetOwner(int row, int col, OpenDirection dir, GamePlayer player)
        {
            var region = GetRegion(row, col, dir);
            if (!TestSetOwner(region, player))
                throw new Exception("cannot take owner on this region.");

            region.PlayerTakeOwner(new PlayerPuppet
            {
                Player = player,
                Position = new OpenEdge
                {
                    Row = row,
                    Column = col,
                    OpenDirection = dir
                }
            });
        }

        //public void SetOwnership(RegionInMap region, GamePlayer player)
        //{
        //    if (!TestSetOwner(region, player))
        //        throw new Exception("cannot take ownership");
        //    region.Owners.Add(player);

        //    player.PuppetRemain++;
        //    player.OwnedRegionCount++;
        //}

        public RegionInMap GetRegion(int row, int column, OpenDirection dir)
        {
            var cardim = GetCard(row, column);
            
            if (cardim == null)
                return null;

            // church
            if (dir == OpenDirection.Center)
            {
                if (cardim.Card.CardType == CardType.Church)
                {

                    var retregion = (from region in Regions
                                     where region.Cards.First().Equals(cardim)
                                     && region.RegionCardType == CardType.Church
                                     select region
                            ).First();
                    return retregion;
                }
                else
                    return null;
            }
            else
            {
                var curedge = cardim.GetEdge(dir);

                // filter off empty edge
                if (curedge.EdgeCardType == CardType.Church ||
                    curedge.EdgeCardType == CardType.Empty)
                    return null;

                var selectregions = (from region in Regions
                                     where region.Cards.Contains(cardim)
                                     && region.RegionCardType != CardType.Church
                                     select region).ToList();

                // no region
                if (selectregions.Count == 0)
                    return null;

                var sidecardim = GetSideCard(cardim, dir);
                if (sidecardim != null)
                {
                    return (from region in selectregions
                            where region.Cards.Contains(sidecardim)
                            select region).First();
                }
                else
                {
                    return (from region in selectregions
                            where region.OpenEdges.Any(e => e.IsEquals(curedge))
                            select region).First();
                }
            }
        }

        #region navigation methods

        public CardInMap GetSideCard(CardInMap cardim, OpenDirection dir)
        {
            switch (dir)
            {
                case OpenDirection.Left:
                    return GetCard(cardim.Row, cardim.Column - 1);
                case OpenDirection.Top:
                    return GetCard(cardim.Row - 1, cardim.Column);
                case OpenDirection.Right:
                    return GetCard(cardim.Row, cardim.Column + 1);
                case OpenDirection.Bottom:
                    return GetCard(cardim.Row + 1, cardim.Column);
                default:
                   throw new Exception("shouldn't be called");
            }
        }

        List<CardInMap> GetAroundCards(CardInMap cardim)
        {
            var list = new List<CardInMap>();
            for (int i = cardim.Row - 1; i <= cardim.Row + 1; i++)
                for (int j = cardim.Column - 1; j <= cardim.Column + 1; j++)
                {
                    var mycard = GetCard(i, j);
                    if (mycard != null)
                        list.Add(mycard);
                }
            list.Remove(cardim);

            return list;
        }
        #endregion

        #region Card Set Get
        public bool TestSetCard(CardInMap cardim)
        {
            // test if the position is set card already..
            var curcardim = GetCard(cardim.Row, cardim.Column);
            if (curcardim != null)
                return false;

            bool hassidecard = false;

            curcardim = GetSideCard(cardim,OpenDirection.Left);
            if (curcardim != null)
            {
                hassidecard = true;
                if (!cardim.GetEdge(OpenDirection.Left)
                    .IsConnectable(curcardim.GetEdge(OpenDirection.Right)))
                    return false;
            }

            curcardim = GetSideCard(cardim, OpenDirection.Top);
            if (curcardim != null)
            {
                hassidecard = true;
                if (!cardim.GetEdge(OpenDirection.Top)
                    .IsConnectable(curcardim.GetEdge(OpenDirection.Bottom)))
                    return false;
            }

            curcardim = GetSideCard(cardim, OpenDirection.Right);
            if (curcardim != null)
            {
                hassidecard = true;
                if (!cardim.GetEdge(OpenDirection.Right)
                    .IsConnectable(curcardim.GetEdge(OpenDirection.Left)))
                    return false;
            }

            curcardim = GetSideCard(cardim, OpenDirection.Bottom);
            if (curcardim != null)
            {
                hassidecard = true;
                if (!cardim.GetEdge(OpenDirection.Bottom)
                    .IsConnectable(curcardim.GetEdge(OpenDirection.Top)))
                    return false;
            }

            // doesn't near to any existed card, nor is the first card.
            if ((!hassidecard) && SetCardCount > 0)
                return false;
            
            return true;

            //if (cardim.GetEdge(OpenDirection.Left)
            //    .IsConnectable(GetLeftCard(cardim).GetEdge(OpenDirection.Right))
            //    && cardim.GetEdge(OpenDirection.Top)
            //    .IsConnectable(GetTopCard(cardim).GetEdge(OpenDirection.Bottom))
            //    && cardim.GetEdge(OpenDirection.Right)
            //    .IsConnectable(GetRightCard(cardim).GetEdge(OpenDirection.Left))
            //    && cardim.GetEdge(OpenDirection.Bottom)
            //    .IsConnectable(GetBottomCard(cardim).GetEdge(OpenDirection.Top)))
            //    return true;
            //else
            //    return false;
        }

        int SetCardCount;
        public void SetCard(CardInMap cardim,GamePlayer player)
        {
            int row = cardim.Row;
            int column = cardim.Column;
            if (TestSetCard(cardim))
            {
                SetCardCount++;
                Cards[row, column] = cardim;

                RegionUpdate(cardim, player);
            }
            else
                throw new Exception("Cannot put card here.");
        }

        public CardInMap GetCard(int row, int column)
        {
            if (row < 0 || column < 0)
                return null;
            return Cards[row, column];
        }
        #endregion

        public event EventHandler<RegionCompleteEventArgs> RegionCompleted;
        void RegionUpdateState(RegionInMap region)
        {
            var result = region.UpdateState();
            if (result.IsCompleted)
            {
                foreach (var owner in region.OwnerPuppets)
                    owner.Player.PuppetRemain++;
                if (RegionCompleted != null)
                {
                    RegionCompleted(this, new RegionCompleteEventArgs
                    {
                        Region = region,
                        Result = result
                    });
                }
            }
        }

        public event EventHandler<RegionMergeEventArgs> RegionMerged;
        void RegionMerge(RegionInMap regiona, RegionInMap regionb)
        {
            var result = regiona.MergeRegion(regionb);
            if (result.IsMerged && RegionMerged!=null)
            {
                RegionMerged(this, new RegionMergeEventArgs
                {
                    Result = result
                });
            }
        }

        //
        void RegionUpdate(CardInMap curcardim, GamePlayer player)
        {
            if (curcardim == null)
                return;

            int row = curcardim.Row;
            int column = curcardim.Column;

            // church part
            var myregions = Regions.Where(r => r.RegionCardType == CardType.Church);
            foreach (var myregion in myregions)
            {
                // get region first card as central card
                var myregioncardim = myregion.Cards.First();

                // check if in region
                if (row >= myregioncardim.Row - 1 && row <= myregioncardim.Row + 1
                        && column >= myregioncardim.Column - 1 && column <= myregioncardim.Column + 1
                        && !myregion.Cards.Contains(curcardim))
                {
                    myregion.Cards.Add(curcardim);
                    RegionUpdateState(myregion);
                }
            }

            // church
            if (curcardim.Card.CardType == CardType.Church)
            {
                var newregion = new RegionInMap();
                newregion.RegionCardType = CardType.Church;

                GetAroundCards(curcardim).ForEach(
                    c => newregion.Cards.Add(c));

                // put at first index 0.
                newregion.Cards.Insert(0,curcardim);
                newregion.Creator = player;

                RegionUpdateState(newregion);

                Regions.Add(newregion);
            }

            // append castle,road
            OpenDirection[] directions = {   OpenDirection.Left,
                                 OpenDirection.Top,
                                 OpenDirection.Right,
                                 OpenDirection.Bottom
                             };
            List<OpenDirection> handleddirection = new List<OpenDirection>();

            Dictionary<OpenDirection, RegionInMap> directionregion 
                = new Dictionary<OpenDirection, RegionInMap>();

            foreach (var curdir in directions)
            {
                List<RegionInMap> matchregions =new List<RegionInMap>();
                //List<OpenEdge> matchregionedges = new List<OpenEdge>();
                //List<OpenEdge> matchcardedges = new List<OpenEdge>();

                // skip handled
                if (handleddirection.Contains(curdir))
                    continue;

                // if is empty border, skip
                var curedge = curcardim.GetEdge(curdir);
                if (curedge.EdgeCardType == CardType.Empty)
                    continue;

                var curedges = curcardim.GetInnerConnectEdges(curdir);
                var desirededges = curedges.Select(e => e.GetMatchedEdge()).ToList();

                // mark handled
                curedges.ForEach(e => handleddirection.Add(e.OpenDirection));

                // append
                foreach (var myregion in Regions)
                {
                    for (int i = myregion.OpenEdges.Count - 1; i >= 0; i--)
                    {
                        for (int j = desirededges.Count - 1; j >= 0; j--)
                        {
                            var myedge = myregion.OpenEdges[i];
                            var desirededge = desirededges[j];
                            // found match
                            if (myedge.IsEquals(desirededge))
                            {
                                myregion.OpenEdges.RemoveAt(i);
                                desirededges.RemoveAt(j);
                                curedges.RemoveAt(j);

                                // in case of circle
                                if (!myregion.Cards.Contains(curcardim))
                                    myregion.Cards.Add(curcardim);

                                matchregions.Add(myregion);

                                break;
                            }
                        }
                    }
                }

                // append
                if (matchregions.Count >0)
                {
                     // merge
                    if (matchregions.Count > 1)
                    {
                        var firstregion = matchregions[0];

                        for (int i = matchregions.Count - 1; i >= 1; i--)
                        {
                            // circle self
                            if (firstregion == matchregions[i])
                                continue;

                            RegionMerge(firstregion, matchregions[i]);
                            this.Regions.Remove(matchregions[i]);
                        }
                    }

                    // add open edge
                    curedges.ForEach(e => matchregions[0].OpenEdges.Add(e));

                    RegionUpdateState(matchregions[0]);
                }
                else
                // add new
                {
                    var newregion = new RegionInMap();
                    newregion.RegionCardType = curedge.EdgeCardType;

                    newregion.Cards.Add(curcardim);

                    newregion.Creator = player;

                    curedges.ForEach(e => newregion.OpenEdges.Add(e));

                    Regions.Add(newregion);
                    directionregion[curdir] = newregion;

                    RegionUpdateState(newregion);
                }
            }
        }
    }

    public class RegionCompleteEventArgs : EventArgs
    {
        public RegionInMap Region { set; get; }
        public RegionUpdateResult Result { set; get; }
    }

    public class RegionMergeEventArgs : EventArgs
    {
        public RegionMergeResult Result { set; get; }
    }
}
